﻿//-----------------------------------------------------------------------
// <copyright file="CanvasRendererBase.cs" company="Nathan Miller">
// Copyright (c) Nathan Miller.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
//-----------------------------------------------------------------------
namespace TileTool.Render
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using TileTool.Common.Math;

    /// <summary>
    /// Base renderer class that takes care of some of the ICanvasRenderer 
    /// interface implementation.
    /// </summary>
    public abstract class CanvasRendererBase
    {
        #region Constants - Private

        /// <summary>
        /// Max transform stack size.
        /// </summary>
        private const int MaxTransformCount = 32;

        #endregion

        #region Fields - Private

        /// <summary>
        /// Transform stack.
        /// </summary>
        private IList<Matrix2D> transformStack = new List<Matrix2D>();

        /// <summary>
        /// Render state stack.
        /// </summary>
        private IList<CanvasRendererState> renderStateStack = new List<CanvasRendererState>();

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor for the base renderer.
        /// </summary>
        public CanvasRendererBase()
        {
            // Always push the identity matrix onto the stack so we 
            // always have something there.
            //
            // Don't call PushMatrix() since it calls virtual functions.
            this.transformStack.Add(Matrix2D.Identity);

            // Always push a default render state onto the stack
            //
            // Don't call PushState() since it calls virtual functions.
            this.renderStateStack.Add(new CanvasRendererState());
        }

        #endregion

        #region Properties - Public

        /// <summary>
        /// Gets the renderer's current transform.
        /// </summary>
        public Matrix2D Transform
        {
            get
            {
                Debug.Assert(this.transformStack.Count != 0, "Transform stack must not be empty.");
                return this.transformStack[this.transformStack.Count - 1];
            }
        }

        /// <summary>
        /// Gets or sets if anti-aliasing should be used.
        /// </summary>
        public bool AntiAlias
        {
            get
            {
                return this.State.AntiAlias;
            }

            set
            {
                if (this.State.AntiAlias != value)
                {
                    this.State.AntiAlias = value;
                    this.AntiAliasChanged();
                }
            }
        }

        #endregion

        #region Properties - Protected

        /// <summary>
        /// Gets the current renderer state.
        /// </summary>
        protected CanvasRendererState State
        {
            get
            {
                Debug.Assert(this.renderStateStack.Count != 0, "Render state stack must not be empty.");

                return this.renderStateStack[this.renderStateStack.Count - 1];
            }
        }

        #endregion

        #region Methods - Public

        /// <summary>
        /// Push a matrix onto the transform stack.
        /// </summary>
        /// <param name="mat">Matrix to push onto the stack.</param>
        public void PushMatrix(Matrix2D mat)
        {
            if (this.transformStack.Count == MaxTransformCount)
            {
                throw new InvalidOperationException("Max matrix stack size reached: " + MaxTransformCount);
            }

            if (this.transformStack.Count != 0)
            {
                mat = this.transformStack[this.transformStack.Count - 1].Multiply(mat);
            }

            this.transformStack.Add(mat);

            this.TransformChanged();
        }

        /// <summary>
        /// Pop a matrix off of the transform stack.
        /// </summary>
        public void PopMatrix()
        {
            if (this.transformStack.Count == 1)
            {
                throw new InvalidOperationException("Attempting to pop default identity matrix off the transform stack.");
            }

            this.transformStack.RemoveAt(this.transformStack.Count - 1);

            this.TransformChanged();
        }

        #endregion

        #region Methods - Render state

        /// <summary>
        /// Push a new render state onto the state stack.
        /// </summary>
        public void PushState()
        {
            if (this.renderStateStack.Count == 0)
            {
                throw new InvalidOperationException("Attempting to push a render state onto an empty state. It must never be empty.");
            }

            // Make the new state be a copy of the current.
            CanvasRendererState newState = new CanvasRendererState(this.renderStateStack[this.renderStateStack.Count - 1]);

            this.renderStateStack.Add(newState);
        }

        /// <summary>
        /// Pop a render state off of the state stack.
        /// </summary>
        public void PopState()
        {
            if (this.renderStateStack.Count == 1)
            {
                throw new InvalidOperationException("Attempting to pop default render state off the render state stack.");
            }

            CanvasRendererState oldState = this.State;

            this.renderStateStack.RemoveAt(this.renderStateStack.Count - 1);

            CanvasRendererState newState = this.State;

            this.HandleRenderStateChanged(oldState, newState);
        }

        #endregion

        #region Methods - Protected

        /// <summary>
        /// Called when the render transform changes.
        /// </summary>
        protected virtual void TransformChanged()
        {
        }

        /// <summary>
        /// Called when the state of anti-aliasing changed.
        /// </summary>
        protected virtual void AntiAliasChanged()
        {
        }

        #endregion

        #region Methods - Private

        /// <summary>
        /// Called when a render state is popped off the render state stack.
        /// </summary>
        /// <param name="oldState">Old render state.</param>
        /// <param name="newState">New render state.</param>
        private void HandleRenderStateChanged(CanvasRendererState oldState, CanvasRendererState newState)
        {
            if (oldState.AntiAlias != newState.AntiAlias)
            {
                this.AntiAliasChanged();
            }
        }

        #endregion
    }
}
